<?php

abstract class OpenAPIRequest{
	/**
	 *  @var string $uri 上次调用API接口的URI.
	 */
	public $uri;
	
	/**
	 * @var int HTTPMETHOD_POST $httpmethod使用POST方法传递API参数，$httpmethod默认使用HTTPMETHOD_POST
	 */ 
	const  HTTPMETHOD_POST=0; 
	/**
	 * @var int HTTPMETHOD_POST $httpmethod使用GET方法传递API参数
	 */ 	
	const  HTTPMETHOD_GET=1;
	/**
	 * @var int HTTPMETHOD_POSTWITHFILE $httpmethod使用POST方法传递API参数，并且还要上传文件<br/>
	 * 如上传图像，array('image' => '/tmp/cs.jpg')
	 */ 		
	const  HTTPMETHOD_POSTWITHFILE=2;
	/**
	 * 
	 * @var int $httpmethod 传递API参数的方法，其值是：@see HTTPMETHOD_POST,@see HTTPMETHOD_GET,@see HTTPMETHOD_POSTWITHFILE <br/>
	 * 默认为HTTPMETHOD_POST，如果参数不合法，使用HTTPMETHOD_POST。
	 */
	public $httpmethod=0;
	/**
	 * @var array $postWithFiles 当$httpmethod=HTTPMETHOD_POSTWITHFILE时，使用此参数设置的文件上传<br/>
	 * 如上传图像，array('image' => '/tmp/cs.jpg')
	 */
	public $postWithFiles=array();
	
	public $err_no=0;
	public $err_msg=NULL; 
	
	public $use_curl=true;
	
    function __construct(){
    	$this->httpmethod=self::HTTPMETHOD_POST;
    	$this->use_curl=$this->use_curl && extension_loaded('curl');
    }
 
    /**
     * 返回API结果数组，在get_array基础上添加重试，重试时间100~300毫秒。
     * 建议调用此数组，重试仅对可 重调用数据不变接口函数 使用，如只读。
	 * @param string $cmdid 要调用的api接口的cmdid字符串，如果为空，将返回false.
	 * @param array $params 要调用的api接口的参数数组，一般通用的参数不要传，如charset，format等。
	 * @param boolean $retry 是否重试，默认true，仅对可 重调用数据不变接口函数 使用，如只读。
	 * @param string $resp_handle 用于额外处理get_resp函数返回字符串的函数名称，此函数调用在调用get_resp函数后，解析为数组前，默认为NULL不调用。
	 * @return array|false  结果数组，如果失败或API错误返回false，错误码/错误消息见@see $err_no/$err_msg，调用失败err_no:-1。
     */
	function & get($cmdid,array  $params,$retry=true,$resp_handle=NULL){
		$this->err_no=0;
		$this->err_msg=NULL;
		$idx= $retry ? 1 : 0;
		do { 
			$ex=NULL;
			try {
				$arr = &$this->get_array ( $cmdid, $params, $resp_handle );
				$idx=-1;
			} catch ( Exception $e ) {
				$ex=$e;
				--$idx;
			}
			if ($retry && $idx>=0) { 
				$ms = rand ( 100, 300 );
				usleep ( $ms );
			}
		} while ( $retry && $idx>=0);
		if($ex){
			$this->err_no=-1;
			$this->err_msg=$ex->getCode().','.$ex->getMessage();
			$f=false;return $f;
		} 
	 	return $arr;
	}	 
	
	/**
	 * 得到 API的返回值，JSON,XML字符串，如果失败将抛出异常。
	 * @param string $cmdid 要调用的api接口的id字符串.
	 * @param array $params 要调用的api接口的参数数组。
	 * @return string|false 调用的api接口得到的JSON,XML字符串，失败：抛出异常。
	 */
	 abstract function &get_resp($cmdid,array $params);	
	
	/**
	 * 得到 API的返回数组，如果失败将抛出异常。
	 * @param string $cmdid  要调用的api接口的id字符串.
	 * @param array $params 要调用的api接口的参数数组。
	 * @param string $resp_handle 用于额外处理get_resp函数返回字符串的函数名称，此函数调用在调用get_resp函数后，解析为数组前，默认为NULL不调用。
	 * @return array|false 返回调用的api接口得到的数组，API错误返回false，如果失败将抛出异常。
	 */		 
	 abstract protected function &get_array($cmdid,array  $params,$resp_handle=NULL);
  

	/**
	 * @deprecated
	 */
	function &getArray($cmdid,array  $params,$resp_handle=NULL){
		$a=& $this->get_array($cmdid,$params,$resp_handle);
		return $a;
	}
		 
	/**
	 * 发出请求数据，得到Open API的返回字符串。
	 * @param string $url 要调用的Open API接口的url.
	 * @param array $paramArr 要调用的api接口的参数数组。
	 * @return string|bool 调用的Open API接口得到的字符串。
	 */
	protected function &send(&$url,array & $paramArr,$http_method=false){
		$this->uri = $url;
		if($http_method ===false ) $http_method=$this->httpmethod;
		//send rpc data
		if($this->use_curl){
			$paramArr=& $this->_prepareParam($paramArr);
			if($http_method == self::HTTPMETHOD_POSTWITHFILE){
				foreach($this->postWithFiles as $k=>$v)
					$paramArr[$k]='@'.$v;
			}			
			$curl = curl_init();
			curl_setopt($curl, CURLOPT_URL, $this->uri);
			curl_setopt($curl, CURLOPT_POST,$http_method !== self::HTTPMETHOD_GET);
			curl_setopt($curl, CURLOPT_POSTFIELDS, $paramArr);
			curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
			
			$result=curl_exec($curl);
			if(curl_errno($curl)){
				$msg=curl_error($curl);
				curl_close($curl);
				if(isset($GLOBALS['context'])) $GLOBALS['context']->log_error($msg);
				throw new Exception($msg);
			}else{
				curl_close($curl);
				return $result;
			}	
		}else{
			//load Snoopy file
			if(isset($GLOBALS['context']))	require_lib('net/Snoopy.class',false);
			else{
	 			$snp=dirname(__FILE__).'/Snoopy.class.php';
	 			if(file_exists($snp)) require_once $snp;
			}			
	        
	        if ($http_method == self::HTTPMETHOD_GET)   	$paramArr=$this->_createStrParam($paramArr); 
	        else $paramArr= & $this->_prepareParam($paramArr);
	        
	        $snoopy = new Snoopy();
	        if ($http_method == self::HTTPMETHOD_GET){
	          $this->uri =$url . '?' . $paramArr;      
	          $snoopy->fetch($this->uri);
	        } else{
				if ($http_method== self::HTTPMETHOD_POSTWITHFILE 
	               && count($this->postWithFiles)>0){
	          		$snoopy->_submit_type='multipart/form-data';
	         	 	$snoopy->submit($this->uri,$paramArr,$this->postWithFiles);
	        	} else   $snoopy->submit($this->uri,$paramArr); 	        	
	        }
	        
	        if($snoopy->error){
					if(isset($GLOBALS['context'])) $GLOBALS['context']->log_error($snoopy->error);
                    throw new Exception($snoopy->error);
                }
	        return $snoopy->results;
		}
	}
	
	/*
	 * 组参函数 ,内部用
	 */
	private function &_createStrParam(array &$paramArr) {
		$strParam = '';
		foreach ( $paramArr as $key => $val ) {
			if ($key != '' && isset($val)) {
				if(is_array($val)) {
					if( count($val)>0)
					foreach ($val as $item)
					  $strParam .= $strParam ? '&' & $key . '=' . urlencode( $item ) : $key . '=' . urlencode( $item );
				}
				else 
				$strParam .= $strParam ? '&' . $key . '=' . urlencode( $val ) : $key . '=' . urlencode( $val );
			}
		}
		return $strParam;
	}	
	private function &_prepareParam(array &$paramArr) { //trim empty value
		$Paramok =array();
		foreach ( $paramArr as $key => $val ) {
			if ($key != '' && isset($val)) {
				if(is_array($val)) {
					$itemarr=array();
					foreach ($val as $item)
					  if($item != '')  $itemarr[]=$item;
					$Paramok[$key] = $itemarr ;
				}
				else 
				$Paramok[$key] = $val; //Snoopy类在执行submit函数时，它自动会将参数做urlencode编码
			}
		}
		return $Paramok;
	}
	
	protected function & _to_sign_str(array $paramArr){
		ksort ( $paramArr );
		$sign='';
		foreach ( $paramArr as $key => $val ) {
			if ($key != '' && isset($val)) {
				if (is_array ( $val )) {
					if(count ( $val ) > 0)
					foreach ( $val as $item )
						$sign .= $key . $item;
				} 
				else
					$sign .= $key . $val;
			}
		}
		return 	$sign;  	
	}
		
}